// /**********************************************************************
// 参考地址
// http://www.taichi-maker.com/homepage/esp8266-nodemcu-iot/iot-tuttorial/mqtt-tutorial/esp8266-publish-mqtt/

// PUBLISH – 发布消息
// 1-5 发布、订阅和取消订阅
// http://www.taichi-maker.com/homepage/esp8266-nodemcu-iot/iot-tuttorial/mqtt-tutorial/publish-subscribe-unsubscribe-mqtt-msg/

// topicName – 主题名
// 主题名用于识别此信息应发布到哪一个主题。关于MQTT主题的应用，我们在之前的课程中已经做了详细介绍，在后续课程中我们还会对主题的高级应用进行更加详细的讲解。
// QoS – 服务质量等级
// QoS（Quality of Service）表示MQTT消息的服务质量等级。QoS有三个级别：0、1和2。QoS决定MQTT通讯有什么样的服务保证。有关QoS的详细信息我们会在后续课程中详细讲解。
// packetId – 报文标识符
// 报文标识符可用于对MQTT报文进行标识。不同的MQTT报文所拥有的标识符不同。MQTT设备可以通过该标识符对MQTT报文进行甄别和管理。请注意：报文标识符的内容与QoS级别有密不可分的关系。只有QoS级别大于0时，报文标识符才是非零数值。如果QoS等于0，报文标识符为0。
// retainFlag – 保留标志
// 在默认情况下，当客户端订阅了某一主题后，并不会马上接收到该主题的信息。只有在客户端订阅该主题后，服务端接收到该主题的新信息时，服务端才会将最新接收到的该主题信息推送给客户端。
// 但是在有些情况下，我们需要客户端在订阅了某一主题后马上接收到一条该主题的信息。这时候就需要用到保留标志这一信息。关于保留标志的具体使用方法，我们将在本教程的后续部分进行详细讲解。
// Payload – 有效载荷
// 有效載荷是我们希望通过MQTT所发送的实际内容。我们可以使用MQTT协议发送文本，图像等格式的内容。这些内容都是通过有效載荷所发送的。
// dupFlag – 重发标志
// 当MQTT报文的接收方没有及时确认收到报文时，发送方会重复发送MQTT报文。在重复发送MQTT报文时，发送方会将此“重发标志”设置为true。请注意，重发标志只在QoS级别大于0时使用。有关QoS的详细信息，我们将会在后续教程中为您做详细介绍。

// SUBSCRIBE – 订阅主题
// 请留意，一个SUBSCRIBE报文可以包含有单个或者多个订阅主题名。也就是说，一个SUBSCRIBE报文可以用于订阅一个或者多个主题。

// 2. 主题分级
// http://www.taichi-maker.com/homepage/esp8266-nodemcu-iot/iot-tuttorial/mqtt-tutorial/mqtt-topics/

// 我们再来看几个分级主题的示例：
// home/sensor/kitchen/temperature
// home/sensor/kitchen/brightness
// home/sensor/bedroom/temperature
// home/sensor/bedroom/brightness

// 3 主题通配符
// 单级通配符: +
// home/sensor/+/temperature
// 当客户端订阅了以上主题后，它将会收到以下主题的信息内容：
// home/sensor/kitchen/temperature
// home/sensor/bedroom/temperature

// 多级通配符 #
// home/sensor/#
// 当客户端订阅了以上含有”#”的主题后，可以收到以下主题的信息。
// home/sensor/kitchen/temperature
// home/sensor/bedroom/brightness
// home/sensor/data

// – 以$开始的主题
// 以$开始的主题是MQTT服务端系统保留的特殊主题，我们不能随意订阅或者向其发布信息。以下是此类主题的示例：
// $SYS/broker/clients/connected
// $SYS/broker/clients/disconnected
// $SYS/broker/clients/total
// $SYS/broker/messages/sent
// $SYS/broker/uptime

// – 不要用 “/” 作为主题开头
// – 主题中不要使用空格
// – 主题中尽量使用ASCII字符


// SUBACK – 订阅确认
// returnCode – 订阅返回码
// 返回码 Return Code Response
// 0 订阅成功 – QoS 0
// 1 订阅成功- QoS 1
// 2 订阅成功- QoS 2
// 128 订阅失败
// UNSUBSCRIBE – 取消订阅


// 发布MQTT消息示例
// http://www.taichi-maker.com/homepage/esp8266-nodemcu-iot/iot-tuttorial/mqtt-tutorial/esp8266-publish-mqtt/

// 中移物联
// 服务地址

// MQTTS 接入套件支持标准 MQTT V3.1.1 版本，支持TLS加密，接入服务地址如下

// 连接协议	证书	地址	端口	说明
// MQTT	证书下载	mqttstls.heclouds.com	8883	加密接口
// MQTT	-	mqtts.heclouds.com	1883	非加密接口

// 数据点 topic 簇 topic 列表如下：

// 系统topic	用途	QoS	可订阅	可发布
// $sys/{pid}/{device-name}/dp/post/json	设备上传数据点	0/1		√
// $sys/{pid}/{device-name}/dp/post/json/accepted	系统通知"设备上传数据点成功"	0	√	
// $sys/{pid}/{device-name}/dp/post/json/rejected	系统通知"设备上传数据点失败"	0	√	

// 订阅 topic	订阅效果
// $sys/{pid}/{device-name}/dp/post/json/accepted	订阅设备数据点上报成功的消息
// $sys/{pid}/{device-name}/dp/post/json/rejected	订阅设备数据点上报失败的消息
// $sys/{pid}/{device-name}/dp/post/json/+	订阅设备数据点上报结果
// $sys/{pid}/{device-name}/cmd/request/+	订阅设备所有命令消息
// $sys/{pid}/{device-name}/cmd/response/+/+	订阅设备所有命令应答结果消息
// $sys/{pid}/{device-name}/cmd/#	订阅设备所有命令相关消息
// $sys/{pid}/{device-name}/#	订阅设备所有相关消息

// 设备镜像 topic 簇

// 设备镜像 topic 簇 topic 列表如下：

// 系统topic	用途	QoS	可订阅	可发布
// $sys/{pid}/{device-name}/image/update	设备更新镜像中属性	0/1		√
// $sys/{pid}/{device-name}/image/update/accepted	系统通知"设备镜像更新成功"	0	√	
// $sys/{pid}/{device-name}/image/update/rejected	系统通知"设备镜像更新失败"	0	√	
// $sys/{pid}/{device-name}/image/update/delta	系统通知"属性中上报值与期望值存在差异"	0	√	
// $sys/{pid}/{device-name}/image/get	设备镜像信息查询	0/1		√
// $sys/{pid}/{device-name}/image/get/accepted	系统通知"设备镜像查询成功"	0	√	
// $sys/{pid}/{device-name}/image/get/rejected	系统通知"设备镜像查询失败"	0	√	

// 设备命令 topic 簇 topic 列表如下：

// 系统topic	用途	QoS	可订阅	可发布
// $sys/{pid}/{device-name}/cmd/request/{cmdid}	系统向设备下发命令	0	√	
// $sys/{pid}/{device-name}/cmd/response/{cmdid}	设备回复命令应答	0/1		√
// $sys/{pid}/{device-name}/cmd/response/{cmdid}/accepted	系统回复"设备命令应答成功"	0	√	
// $sys/{pid}/{device-name}/cmd/response/{cmdid}/rejected	系统回复"设备命令应答失败"	0	√	
// 当应用调用通过命令 API 创建设备命令时，设备必须处于在线状态
// 设备需提前订阅系统 topic 以获取该命令消息
// 设备可以通过发布消息至系统 topic 的方式，进行设备命令结果应答
// 设备进行命令应答之后，平台发送命令应答结果通知，设备需提前订阅系统 topic 以获取该消息
// API返回为同步返回，即，当设备进行命令应答之后，或者当应答超时后，系统返回API请求结果，见命令API详情
// 设备命令下发


// 应用可通过 API 创建下发命令，平台会将该命令消息发布至系统 topic： $sys/{pid}/{device-name}/cmd/request/{cmdid}，若设备订阅了该 topic 则可获取该命令消息，用户可自定义消息内容

// 说明：

// cmdid 为平台为该命令自动创建的唯一标识
// 命令消息内容由应用与设备协商确定，平台将该消息存放于 publish payload 中进行数据透传
// 推荐设备采用通配符方式进行命令请求消息订阅：如 $sys/{pid}/{device-name}/cmd/request/+
// 设备命令回复

// 设备接收该命令后，可通过发布消息至 topic：$sys/{pid}/{device-name}/cmd/response/{cmdid} 进行该命令结果的应答，用户可自定义应答数据内容

// 说明：

// cmdid 为平台为命令下发时携带的唯一标识，设备应答某一条具体命令时，应将 cmdid 置于 topic 路径中
// 命令应答内容由应用与设备协商确定，设备需将该消息存放于 publish payload 中进行上报
// 应答报文 payload 中，数据长度必须 小于1k
// 设备命令回复时，支持 QoS1，若 publish QoS=1，则平台回复 publish ack

// 命令回复结果通知

// 平台接收该应答，会通过 cmdid 存储该应答数据，并通过系统 topic 进行处理结果通知

// 命令应答成功时，系统通过topic $sys/{pid}/{device-name}/cmd/response/{cmdid}/accepted 发布空消息
// 命令不可重复应答，重复应答的消息将会通过拒绝，通过系统 topic $sys/{pid}/{device-name}/cmd/response/{cmdid}/rejected 返回消息
// 推荐设备采用通配符方式进行命令应答结果消息订阅：如：$sys/{pid}/{device-name}/cmd/response/+/+
// 应答错误码

// 当设备进行命令应答出错时，平台会通过 topic $sys/{pid}/{device-name}/cmd/response/{cmdid}/rejected 发布错误消息

// 其中，错误码列表如下：

// 错误码	错误说明	备注
// 99	maximum payload size exceeded	设备回复的命令响应长度超过限制（1k）
// 100	internal error	服务器内部错误
// 112	cmd response timeout	设备在超时时间后回复命令
// 113	cmd id not found	未找到对应的命令ID（命令ID错误或者命令重复应答）
// 错误消息示例如下：

// {
//     "err_code": 99,
//     "err_msg":"maximum payload size exceeded"
// }


// 参数	是否必须	参数说明
// clientId	是	设备名称
// username	是	平台分配的产品ID
// password	是	填写经过 key 计算的 token


// //文档：华宜佳上云（二） mqtt 订阅测试.note
// //链接：http://note.youdao.com/noteshare?id=1a55b9e20022f8ffbaa88ac9062ada1b&sub=C71B7194D1634EC89C5938744C78B4FD

// ******/

// #include <WiFi.h>
// #include <PubSubClient.h>
// #include <ArduinoJson.h>
// #include <Ticker.h>
// #include "header.h"

// // Ticker计数用变量
// Ticker ticker;
// int count;    
//  void tickerCount(){
//   count++;
// }

// //设置wifi接入信息
// WiFiClient wifiClient;


// //设置中移物联mqtt服务器信息
// // MQTT	证书下载	mqttstls.heclouds.com	8883	加密接口
// // MQTT	-	mqtts.heclouds.com	1883	非加密接口
// PubSubClient mqttClient(wifiClient);
// const char* mqttServer = "mqtts.heclouds.com";
// String mqttClientId  = "EWC01"; //实际上是中移物联的设备名
// String user = "410120"; //实际上是中移物联的产品名410120
// //实际上是一个token  运算方法参考 文档：华宜佳上云（一） 中移物联.note
// //http://note.youdao.com/noteshare?id=a7770b777d9a051fc452aa62f9dc41ec&sub=A3732A2CE02E485DBE6B802C05FD58FB
// String pass = "version=2018-10-31&res=products%2F410120%2Fdevices%2FEWC01&et=1632537016&method=sha1&sign=6Ie6QAnu3dWYHMkcj9za2c9z4cA%3D"; 
// //设备命令上报
// //  系统topic	用途	QoS	可订阅	可发布
// // $sys/{pid}/{device-name}/dp/post/json	设备上传数据点	0/1		√
// // $sys/{pid}/{device-name}/dp/post/json/accepted	系统通知"设备上传数据点成功"	0	√	
// // $sys/{pid}/{device-name}/dp/post/json/rejected	系统通知"设备上传数据点失败"	0	√	
// String deviceBizPubTopic = "$sys/410120/EWC01/dp/post/json";
// // 设备命令下发
// // $sys/{pid}/{device-name}/cmd/request/{cmdid}	系统向设备下发命令	0	√	
// // $sys/{pid}/{device-name}/cmd/response/{cmdid}	设备回复命令应答	0/1		√
// // $sys/{pid}/{device-name}/cmd/response/{cmdid}/accepted	系统回复"设备命令应答成功"	0	√	
// // $sys/{pid}/{device-name}/cmd/response/{cmdid}/rejected	系统回复"设备命令应答失败"	0	√	
// // 应用可通过 API 创建下发命令，平台会将该命令消息发布至系统 topic： $sys/{pid}/{device-name}/cmd/request/{cmdid}，若设备订阅了该 topic 则可获取该命令消息，用户可自定义消息内容
// // 说明：
// // cmdid 为平台为该命令自动创建的唯一标识
// // 命令消息内容由应用与设备协商确定，平台将该消息存放于 publish payload 中进行数据透传
// // 推荐设备采用通配符方式进行命令请求消息订阅：如 $sys/{pid}/{device-name}/cmd/request/+
// // 设备命令回复
// // 设备接收该命令后，可通过发布消息至 topic：$sys/{pid}/{device-name}/cmd/response/{cmdid} 进行该命令结果的应答，用户可自定义应答数据内容
// // 说明：
// // cmdid 为平台为命令下发时携带的唯一标识，设备应答某一条具体命令时，应将 cmdid 置于 topic 路径中
// // 命令应答内容由应用与设备协商确定，设备需将该消息存放于 publish payload 中进行上报
// // 应答报文 payload 中，数据长度必须 小于1k
// // 设备命令回复时，支持 QoS1，若 publish QoS=1，则平台回复 publish ack
// //https://open.iot.10086.cn/doc/mqtt/book/example/cmd.html 教程
// //String deviceCmdRequestSubTopic= "$sys/410120/EWC01/cmd/request/+"; //或 $sys/410120/EWC01/cmd/#
// String deviceCmdRequestSubTopic= "$sys/410120/EWC01/cmd/#";
// //应答对应的topic为： $sys/238322/mqtts-test-device/cmd/response/33ffea0a-e5f1-49d6-a626-ffee1bbd93ef 
// // String deviceCmdResponseSubTopic = ""; //收到什么答什么

// //设备镜像上报
// //什么是设备镜像 https://open.iot.10086.cn/doc/book/device-develop/multpro/MQTTS/image.html
// // 设备镜像 topic 簇 topic 列表如下：
// // 系统topic	用途	QoS	可订阅	可发布
// // $sys/{pid}/{device-name}/image/update	设备更新镜像中属性	0/1		√
// // $sys/{pid}/{device-name}/image/update/accepted	系统通知"设备镜像更新成功"	0	√	
// // $sys/{pid}/{device-name}/image/update/rejected	系统通知"设备镜像更新失败"	0	√	
// // $sys/{pid}/{device-name}/image/update/delta	系统通知"属性中上报值与期望值存在差异"	0	√	
// // $sys/{pid}/{device-name}/image/get	设备镜像信息查询	0/1		√
// // $sys/{pid}/{device-name}/image/get/accepted	系统通知"设备镜像查询成功"	0	√	
// // $sys/{pid}/{device-name}/image/get/rejected	系统通知"设备镜像查询失败"	0	√	

// String deviceImagePubTopic = "$sys/410120/EWC01/image/update";

// // ESP8266连接wifi
// void connectWifi(){
 
//   WiFi.begin(ssid, password);
 
//   //等待WiFi连接,成功连接后输出成功信息
//   while (WiFi.status() != WL_CONNECTED) {
//     delay(1000);
//     Serial.println(" WiFi connect ");  
//     Serial.print(".");
//   }
//   Serial.println(""); 
//   Serial.print(ssid);
//   Serial.println(" WiFi connect Success");  
//   Serial.println(""); 
// }

// // 订阅指定主题
// void subscribeTopic(String topic){
//   //https://open.iot.10086.cn/doc/mqtt/book/device-develop/topics/cmd-topics.html
// //   系统topic	用途	QoS	可订阅	可发布
// // $sys/{pid}/{device-name}/cmd/request/{cmdid}	系统向设备下发命令	0	√	
// // $sys/{pid}/{device-name}/cmd/response/{cmdid}	设备回复命令应答	0/1		√
// // $sys/{pid}/{device-name}/cmd/response/{cmdid}/accepted	系统回复"设备命令应答成功"	0	√	
// // $sys/{pid}/{device-name}/cmd/response/{cmdid}/rejected	系统回复"设备命令应答失败"	0	√	
 
//   // 建立订阅主题。主题名称以Taichi-Maker-Sub为前缀，后面添加设备的MAC地址。
//   // 这么做是为确保不同设备使用同一个MQTT服务器测试消息订阅时，所订阅的主题名称不同

//   char subTopic[topic.length() + 1];  
//   strcpy(subTopic, topic.c_str());

//   Serial.println("sub "+ topic); 
  
//   // 通过串口监视器输出是否成功订阅主题以及订阅的主题名称
//   if(mqttClient.subscribe(subTopic)){
//     Serial.println("Subscrib Topic:");
//     Serial.println(subTopic);
//   } else {
//     Serial.print("Subscribe Fail...");
//   }  
// }

// //连接mqtt服务器
// void connectMQTTServer(){
//   // 连接MQTT服务器
//   if (mqttClient.connect(mqttClientId.c_str(),user.c_str(),pass.c_str())) { 
//     Serial.println("MQTT Server connect Success");
//     Serial.println("MQTT Server Address: ");
//     Serial.println(mqttServer);
//     Serial.println("mqttClientId:");
//     Serial.println(mqttClientId);
//     // 设置订阅
//     subscribeTopic(deviceCmdRequestSubTopic);
//   } else {
//     Serial.println("MQTT Server conntect Failed");
//     Serial.println(mqttClient.state());
//     delay(3000);
//   }   
// }
 

// // 发布设备业务信息
// void pubDeiveBizmsg(){
//   //https://open.iot.10086.cn/doc/mqtt/book/device-develop/topics/dp-topics.html
//   char publishTopic[deviceBizPubTopic.length() + 1];  
//   strcpy(publishTopic, deviceBizPubTopic.c_str());
 
//   // 建立发布信息。信息内容以Hello World为起始，后面添加发布次数。
//   String messageString = "{\"id\":123,\"dp\":{\"temperatrue\":[{\"v\":50,\"t\":1632537016}],\"power\":[{\"v\":12.5,\"t\":1632537016}],\"status\":[{\"v\":{\"color\":\"blue\"},\"t\":1632537016},{\"v\":{\"color\":\"green\"},\"t\":1632537016}]}}"; 
//   // String messageString = {\"id\":123,\"dp\":{\"temperatrue\":[{\"v\":30,\"t\":1632537016}],\"power\":[{\"v\":4.5,\"t\":1632537016}],\"status\":[{\"v\":{\"color\":\"blue\"},\"t\":1632537016},{\"v\":{\"color\":\"red\"},\"t\":1632537016}]}}
//   char publishMsg[messageString.length() + 1];   
//   strcpy(publishMsg, messageString.c_str());
  
//   // 实现ESP8266向主题发布信息
//   if(mqttClient.publish(publishTopic, publishMsg)){
//     Serial.println("Publish Topic:");Serial.println(publishTopic);
//     Serial.println("Publish message:");Serial.println(publishMsg);    
//   } else {
//     Serial.println("Message Publish Failed."); 
//   }
// }

// // 发布设备镜像信息
// void pubDeiveImageMsg(){
//   //https://open.iot.10086.cn/doc/book/device-develop/multpro/MQTTS/image.html
//   //https://open.iot.10086.cn/doc/mqtt/book/device-develop/topics/image-topics.html
//   char publishTopic[deviceImagePubTopic.length() + 1];  
//   strcpy(publishTopic, deviceImagePubTopic.c_str());
// //   {
// //     "state": {
// //         "reported": {
// //             "color": "red"
// //         }
// //     }
// // }
//   StaticJsonDocument<500> doc;
//   JsonObject state =  doc.createNestedObject("state");
//   JsonObject reported =  state.createNestedObject("reported");
//   reported["color"] = "white";
//   String json;
//   serializeJson(doc, json);
 
//   // 建立发布信息。信息内容以Hello World为起始，后面添加发布次数。
//   char publishMsg[json.length() + 1];   
//   strcpy(publishMsg, json.c_str()); 
  
//   // 实现ESP8266向主题发布信息
//   if(mqttClient.publish(publishTopic, publishMsg)){
//     Serial.println("Publish Topic:");Serial.println(publishTopic);
//     Serial.println("Publish message:");Serial.println(publishMsg);    
//   } else {
//     Serial.println("Message Publish Failed."); 
//   }
// }



// // 收到信息后的回调函数
// void subscribeTopicCallback(char* topic, byte* payload, unsigned int length) {
//   //这里要判断是什么主题，我这里没有写判断
//   Serial.print("Message Received [");
//   Serial.print(topic);
//   Serial.print("] ");
//   for (int i = 0; i < length; i++) {
//     Serial.print((char)payload[i]);
//   }
//   Serial.println("");
//   Serial.print("Message Length(Bytes) ");
//   Serial.println(length);
 
//   if ((char)payload[0] == '1') {     // 如果收到的信息以“1”为开始
//     digitalWrite(BUILTIN_LED, HIGH);  // 则点亮LED。
//     Serial.println("LED ON");
//   } else if((char)payload[0] == '0') {                           
//     digitalWrite(BUILTIN_LED, LOW); // 否则熄灭LED。
//     Serial.println("LED OFF");
//   }

//   //向服务器回复
//   String deviceCmdResponseSubTopic = topic; //收到什么答什么
//   //将request替换为response
//   deviceCmdResponseSubTopic.replace("request","response");

//   char publishTopic[deviceCmdResponseSubTopic.length() + 1];  
//   strcpy(publishTopic, deviceCmdResponseSubTopic.c_str());
 
//   // 建立发布信息。信息内容以Hello World为起始，后面添加发布次数。
//   String messageString = "ok 收到"; 
//   // String messageString = {\"id\":123,\"dp\":{\"temperatrue\":[{\"v\":30,\"t\":1632537016}],\"power\":[{\"v\":4.5,\"t\":1632537016}],\"status\":[{\"v\":{\"color\":\"blue\"},\"t\":1632537016},{\"v\":{\"color\":\"red\"},\"t\":1632537016}]}}
//   char publishMsg[messageString.length() + 1];   
//   strcpy(publishMsg, messageString.c_str());
  
//   // 实现ESP8266向主题发布信息
//   if(mqttClient.publish(publishTopic, publishMsg)){
//     Serial.println("Publish Topic:");Serial.println(publishTopic);
//     Serial.println("Publish message:");Serial.println(publishMsg);    
//   } else {
//     Serial.println("Message Publish Failed."); 
//   }
// }
 
// void setup() {
//   Serial.begin(9600);
 
//   //设置ESP8266工作模式为无线终端模式
//   WiFi.mode(WIFI_STA);
//   // 连接WiFi
//   connectWifi();
  
//   // 设置MQTT服务器和端口号
//   mqttClient.setServer(mqttServer, 1883);

//   // 设置MQTT订阅回调函数
//   mqttClient.setCallback(subscribeTopicCallback);

//   //设置指示灯
//   pinMode(LED_BUILTIN, OUTPUT);

//    // 连接MQTT服务器
//   connectMQTTServer();



//     // Ticker定时对象
//   ticker.attach(1, tickerCount);  

// }
 
// void loop() { 
//   if (mqttClient.connected()) { // 如果开发板成功连接服务器
//     // 每隔3秒钟发布一次信息
//     if (count >= 30){
//       //推送设备业务信息
//       pubDeiveBizmsg();
//       //推送设备的镜像
//       pubDeiveImageMsg();
//       count = 0;
//     }    
//     // 保持心跳
//     mqttClient.loop();
//   } else {                  // 如果开发板未能成功连接服务器
//     connectMQTTServer();    // 则尝试连接服务器
//   }
// }
 
